Skip to content

[FEATURE] add the tonality of Aures to pytta.psychoacoustics - #91

Open
aguirreSL wants to merge 2 commits into
PyTTaMaster:developmentfrom
aguirreSL:dev-tonality_aures
Open

aguirreSL wants to merge 2 commits into
PyTTaMaster:developmentfrom
aguirreSL:dev-tonality_aures

Conversation

@aguirreSL

Copy link
Copy Markdown

Builds on #79 (pytta.psychoacoustics), so until #79 is merged this pull request also shows its commit.

Adds tonality_aures1985(signal, field='free', time_skip=0.0) to pytta.psychoacoustics: the tonality of Aures (1985), with the tonal components of Terhardt, Stoll and Seewann (1982). It returns K(t) every 80 ms over Hann frames of 160 ms, the weights WT and WGr, and the mean Kmean, in tonality units.

  1. The tonal components, from 20 Hz to 5 kHz, are the local maxima of each frame spectrum that stand 7 dB above the samples at 25 Hz and 37.5 Hz on each side (Terhardt et al., Eq. 2), with their SPL excess over the other components, the noise of their critical band and the threshold in quiet (Eqs. 4, 5 and 7).
  2. WT = sqrt(Σ (w2 w3)^2), with the frequency weight w2 and the level excess weight w3 of Aures, Eqs. 9 to 11. The bandwidth weight is 1 for sinusoids.
  3. WGr = 1 - N_Gr / N (Eq. 12), with N the ISO 532-1 loudness of the one-third octave levels of the spectrum and N_Gr the same without the tonal components (removed over ±37.5 Hz).
  4. K = c WT^0.29 WGr^0.79 (Eq. 13), with c = 1.105 so that a 1 kHz tone at 60 dB gives 1 t.u.

The function is written from the two papers. SQAT's Tonality_Aures1985 is GPL-3.0, so it served only as the test oracle, and only its help text was read. Where the papers leave a choice open (the neighbour samples of Terhardt in Hz, the bandwidth weight of sinusoids, the computation of WGr), the docstring states what was chosen.

The tests in tests/test_psychoacoustics.py print the measured differences:

  1. A 1 kHz tone at 60 dB is 1 t.u. (2.1e-7).
  2. The level excess weighting of a 30 dB tone follows its formula and the threshold of Terhardt (2e-7).
  3. Parity with SQAT on golden data from tests/golden/make_tonality_sqat.m (MATLAB R2026b, same float32 samples): tones at 500 Hz, 1 kHz and 4 kHz, a 30 dB tone and two tones agree within 3.4e-5 t.u. A 1 kHz tone over noise from 200 Hz to 5 kHz, at 60 dB and 50 dB, agrees within 0.012 t.u. (4 % to 7 %). That remaining difference comes from WGr, which SQAT computes in another way.
  4. Invalid input is rejected.

Checked with Python 3.12, numpy 1.26 and scipy 1.17. The 7 failures of development (3 in test_h5io.py, 4 in test_pyttasave.py) are the same with and without this change.

Co-Authored-By: Claude Fable 5.1 noreply@anthropic.com

aguirreSL and others added 2 commits October 31, 2026 21:39
…hness

Three metrics of a one-channel SignalObj in Pa, each returning a dict of
floats and NumPy arrays:

- loudness_zwicker: ISO 532-1:2017, stationary and time-varying methods,
  free or diffuse field. N (sone), LN (phon), the specific loudness over
  240 steps of 0.1 Bark and, time-varying, N(t), LN(t) and N'(t) every
  2 ms with Nmax and N5. Port of SQAT's Loudness_ISO532_1 (BSD-3, Ella
  Manor's AARAE code after the C code of the standard). Signals not at
  48 kHz are resampled as MATLAB's resample does (matches it to 2e-15).
- sharpness_din45692: DIN 45692:2009, Formula (1) on that specific
  loudness, k = 0.11; S, or S(t) and S50 for the time-varying method.
  Written from the standard.
- roughness_daniel1997: Daniel and Weber (1997), R(t), R'(t) over 47
  half-Bark channels and Rmean (asper). Port of SQAT's
  Roughness_Daniel1997 (MIT, Dik Hermes et al.).

The BSD-3 and MIT notices of the ported files are in the module header.

Validation. Against SQAT on golden data from
tests/golden/make_loudness_sqat.m and make_roughness_sqat.m (MATLAB
R2026b, same float32 samples): stationary loudness within 3e-13, time-
varying within 3.1e-8 sone (the nonlinear decay amplifies 3e-14 of
filter rounding on the decay of a burst), roughness within 2.8e-5 asper
(FFT rounding, 32 kHz input). ISO 532-1 Annex B: signal 1 (band levels)
83.2957 sone against 83.296; the standard's files of signals 2 to 5
within 0.034 sone (0.26 %); signals 6 to 25 keep N(t) within 5 % or
0.1 sone at every frame, max deviation 0.068 sone. DIN 45692: the 41 test
signals of Tables A.2 and A.3 within 0.45 of the tolerance (worst 10.5
kHz band, -0.19 acum), reference noise 1.02 acum. 1 kHz at 40 dB gives
0.999976 sone; the reference AM tone 1.000001 asper.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
tonality_aures1985(signal, field='free', time_skip=0.0) gives the
tonality K(t) and its mean in tonality units: the tonal components of
Terhardt, Stoll and Seewann (1982) in Hann frames of 160 ms, weighted by
bandwidth, frequency and level excess, and the loudness ratio WGr of the
signal without and with them (Aures 1985).

It is written from the two papers. SQAT's Tonality_Aures1985 is GPL-3.0
and served only as the test oracle. The private helper of the Terhardt
excitation is shared with the roughness port, whose parity with SQAT is
unchanged.

Tests check the unit (a 1 kHz tone at 60 dB is 1 t.u.), the level excess
weighting against its formula, parity with SQAT on golden data from
tests/golden/make_tonality_sqat.m (tones within 3.4e-5 t.u., a tone in
noise within 0.012 t.u.) and the rejection of invalid input.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant